package com.xxjh.brs.map.service.impl;

import com.xxjh.brs.ftp.common.api.enums.ResponseStatus;
import com.xxjh.brs.ftp.common.exceptions.ConditionException;
import com.xxjh.brs.map.dto.ImageFile;
import com.xxjh.brs.map.dto.MapTileParam;
import com.xxjh.brs.map.dto.RangeParam;
import com.xxjh.brs.map.dto.TileRange;
import com.xxjh.brs.map.service.AbstractMapService;
import com.xxjh.brs.map.utils.MapDownLoadConstants;
import com.xxjh.brs.map.utils.TaskIdGenerator;
import com.xxjh.brs.map.utils.TileCoordinateConverter;
import com.xxjh.brs.map.vo.DownloadProgress;
import com.xxjh.brs.map.vo.DownloadTask;
import com.xxjh.brs.sftp.service.impl.PushSftpService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;

import static com.xxjh.brs.map.utils.MapDownLoadConstants.IMG_SIZE;

/**
 * @author CHENCHEN
 * @title 下载地图服务
 * @company 新兴际华科技（天津）有限公司
 * @create 2024-07-03 19:21
 */
@Slf4j
@Component
public class MapServiceImpl extends AbstractMapService {

    private final ThreadPoolTaskExecutor instantTaskDownloadExecutor;

    private final ThreadPoolTaskExecutor instantTaskUploadExecutor;

    private final int BATCH_SIZE = 16; // 批次大小

    // 使用有界队列存储待上传的路径
    //private final LinkedBlockingDeque<String> pathQueue = new LinkedBlockingDeque<>(10000);

    private final ConcurrentLinkedQueue<String> pathQueue = new ConcurrentLinkedQueue<>();

    @Autowired
    private PushSftpService pushSftpService;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    public MapServiceImpl(ThreadPoolTaskExecutor instantTaskDownloadExecutor, ThreadPoolTaskExecutor instantTaskUploadExecutor) {
        this.instantTaskDownloadExecutor = instantTaskDownloadExecutor;
        this.instantTaskUploadExecutor = instantTaskUploadExecutor;
    }

    /**
     * 获取范围下载进度信息。
     *
     * @param taskId      任务ID。
     * @return 下载进度信息。
     */
    public DownloadProgress getDownloadProgress(long taskId) {
        return downloadProgress(taskId);
    }

    /**
     * 下载远程文件并保存至本地。
     *
     * @param mapTileParam    需要下载的地图参数。
     * @return 如果returnBytes为true，则返回下载的数据字节，否则返回null。
     * @throws IOException 网络或IO异常。
     */
    public ImageFile downloadMapTile(MapTileParam mapTileParam){
        try {
            String localPath = generateTilePathByAlias(mapTileParam.getAlias(), mapTileParam.getX(), mapTileParam.getY(), mapTileParam.getZ());
            File file = new File(localPath);
            byte[] bytes = download(
                    new URL(getUrlPath(mapTileParam.getAlias(), mapTileParam.getX(), mapTileParam.getY(), mapTileParam.getZ())),
                    file,
                    true
            );
            String s = localPath.substring(0, localPath.lastIndexOf("/"));
            pushSftpService.localDirectoryUploadToFtp(mapTileParam.getAlias() + "/", s.split(mapTileParam.getAlias())[1], true);
            return new ImageFile(localPath, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ConditionException(ResponseStatus.MAP_TILE_DOWNLOAD_EXCEPTION);
        }
    }

    /**
     * 根据地图范围和层级下载地图瓦片并返回任务进度ID
     *
     * @param rangeParam      地图范围数组（左下右上）/开始层级/结束层级/alias。
     * @return 下载任务ID。
     */
    public DownloadTask startDownloadTiles(RangeParam rangeParam) {
        if(!taskProgressMap.isEmpty()){
            throw new ConditionException(ResponseStatus.MAP_TASK_RUNING_EXCEPTION);
        }
        long taskId = TaskIdGenerator.generateTaskId();
        DownloadProgress progress = new DownloadProgress(MapDownLoadConstants.calculateTotalTiles(rangeParam.getExtent(), rangeParam.getMinHeight(), rangeParam.getMaxHeight()));
        progress.setTotalSize(TileCoordinateConverter.convertBytesToAppropriateUnit(progress.getTotalTiles() * IMG_SIZE));
        taskProgressMap.put(taskId, progress);
        CompletableFuture.runAsync(() -> downloadMapTilesInBatches(rangeParam.getExtent(), rangeParam.getMinHeight(), rangeParam.getMaxHeight(), rangeParam.getAlias(), taskId), instantTaskDownloadExecutor);
        return new DownloadTask(taskId);
    }

    /**
     * 异步方法，根据地图范围和层级下载地图瓦片。
     *
     * @param extent      地图范围数组（左下右上）。
     * @param minHeight   开始层级。
     * @param maxHeight   结束层级。
     * @param alias 地图类型，影像、矢量...
     */
    private void downloadMapTilesInBatches(double[] extent, int minHeight, int maxHeight, String alias, long taskId) {
        int batchSize = BATCH_SIZE;
        int currentZ = minHeight;
        CompletableFuture.runAsync(() -> rangeUpload(alias));
        try {
            while (currentZ <= maxHeight) {
                List<CompletableFuture<Void>> batchTasks = new ArrayList<>();
                AtomicInteger batchCount = new AtomicInteger(); //用于追踪批次内的任务数量
                for (int z = currentZ; z < Math.min(currentZ + batchSize, maxHeight); z++) {
                    TileRange tileRange = MapDownLoadConstants.calculateTileRange(extent, z);
                    for (int y = tileRange.getYMinRange(); y <= tileRange.getYMaxRange(); y++) {
                        for (int x = tileRange.getXMinRange(); x <= tileRange.getXMaxRange(); x++) {
                            String urlStr = getUrlPath(alias, x, y, z);
                            String path = generateTilePathByAlias(alias, x, y, z);
                            CompletableFuture<Void> task = CompletableFuture.runAsync(() -> {
                                try {
                                    downloadWithRetry(urlStr, path, alias);
                                    batchCount.getAndIncrement();
                                    DownloadProgress progress = taskProgressMap.get(taskId);
                                    if (progress != null) {
                                        progress.incrementDownloaded();
                                    }
                                } catch (IOException e) {
                                    throw new ConditionException(ResponseStatus.MAP_RANGE_DOWNLOAD_EXCEPTION);
                                }
                            }, instantTaskDownloadExecutor).exceptionally(ex -> { // 异步任务的异常处理
                                log.error("Exception in async task: ", ex);
                                throw new ConditionException(ResponseStatus.MAP_RANGE_DOWNLOAD_EXCEPTION);
                            });
                            batchTasks.add(task);
                        }
                        //需要进行上传SFTP，按需选择是否开启
                        setPathToQueue(alias, z, y);
                    }
                }
                // 等待当前批次任务完成
                CompletableFuture.allOf(batchTasks.toArray(new CompletableFuture<?>[0])).join();
                log.info("完成一个批次共计 [{}] 次下载", batchCount.get());
                currentZ += batchSize;
            }
            log.info("范围下载, while循环下载结束....");
        } catch (Exception e) {
            log.error("An error occurred during tile download: ", e);
            // 可能需要根据异常类型做更细粒度的处理
        } finally {
            while (!pathQueue.isEmpty()){

            }
            taskProgressMap.remove(taskId); // 完成后从映射中移除
        }
    }

    /**
     * 将本地下载的地图瓦片路径，存储到队列中。
     *
     * @param z 已下载成功的z。
     */
    public void setPathToQueue(String alias, int z, int y) {
        String localUrl = generateTilePathByAlias(alias, 0, y, z);
        String path = localUrl.substring(0, localUrl.lastIndexOf("/"));
        pathQueue.offer(path.split(alias)[1]);
    }

    public void rangeUpload(String alias){
        while (!taskProgressMap.isEmpty()){
            String path = pathQueue.poll();
            if(!StringUtils.isEmpty(path)){
                PushSftpService sftpService = applicationContext.getBean(PushSftpService.class);
                sftpService.localDirectoryUploadToFtp(alias + "/", path, true);
            }
        }
        log.info("范围下载内容上传完成");
    }
}
